#include "KvPickOp.h"
#include <QThread>
#include <QDirIterator>

class QcWorkspaceIter
{
public:
    QcWorkspaceIter(const QSet<QString>& dirs, bool includeSubDirs) {
        dirIters_.reserve(dirs.size());
        for (auto& i : dirs) {
            auto iter = std::make_unique<QDirIterator>(i, QDir::Files,
                            includeSubDirs ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
            if (iter->hasNext())
                dirIters_.push_back(iter.release());
        }
    }

    ~QcWorkspaceIter() {
        for (auto i : dirIters_)
            delete i;
    }

    bool hasNext() const {
        if (dirIters_.size() > 1)
            return true; // 由于构造函数已剔除空的迭代器，所有此处可直接判断
        else if (dirIters_.empty())
            return false;
        else // size() == 1
            return dirIters_.front()->hasNext();
    }


    QString next() {
        if (dirIters_.empty())
            return QString();

        if (dirIters_.front()->hasNext())
            return dirIters_.front()->next();

        delete dirIters_.front();
        dirIters_.pop_front();
        return next();
    }

private:
    QVector<QDirIterator*> dirIters_; // NOTE: 此处无法用智能指针，因为QDirIterator没有拷贝构造
};


KvPickOp::KvPickOp()
{
  //  connect(this, &KvPickOp::finished, [this](){
  //      cleanFilesQueue_();
  //  });
}


KvPickOp::~KvPickOp()
{
    if (readThread_) {
        stop(true);
        delete readThread_;
        delete calcThread_;
    }

    // TODO: 此处调用cleanFilesQueue_会导致程序崩溃（停止扫描后再开始）
    //cleanFilesQueue_();
    Q_ASSERT(filesQueue_.empty());
}


void KvPickOp::start(const QSet<QString>& workspace, bool includeSubDirs)
{
    if (readThread_) {
        stop(true);
        delete readThread_;
        delete calcThread_;
    }

    readDone_ = false;
    // cleanFilesQueue_();
    Q_ASSERT(filesQueue_.empty());

    // 启动2个线程，一个读取文件，一个执行计算

    // 先启动读取线程
    readThread_ = QThread::create([=](){

        QcWorkspaceIter wkiter(workspace, includeSubDirs);

        forever {
            if (QThread::currentThread()->isInterruptionRequested()) {
                cleanFilesQueue_();
                return;
            }

            if (filesQueue_.size() > maxQueueSize_) {
                QThread::currentThread()->yieldCurrentThread(); // 等待计算，避免队列过大
                continue;
            }

            if (!wkiter.hasNext()) {
                readDone_ = true;
                emit readout();
                return; // all done
            }

            auto filePath = wkiter.next();

            auto pickRes = doPick_(filePath);
            emit filePicked(filePath, pickRes);

            if (pickRes) {
                auto obj = doRead_(filePath);
                emit fileRead(filePath, obj);
            }
        }
    });

    connect(this, &KvPickOp::fileRead, [this](const QString& path, void* obj){
        if (obj) {
            mutex_.lock();
            filesQueue_.push_back({path, obj});
            mutex_.unlock();
        }
    });

    readThread_->start();

    // 再启动计算线程
    calcThread_ = QThread::create([this](){
        forever {
            if (QThread::currentThread()->isInterruptionRequested()) {
                cleanFilesQueue_();
                return;
            }

            if (readDone_ && filesQueue_.isEmpty()) {
                emit alldone();
                break; // all done
            }

            if (filesQueue_.isEmpty()) {
                QThread::currentThread()->yieldCurrentThread(); // 等待读取文件
                continue;
            }

            mutex_.lock();

            // NOTE: 收到中断请求后，readThread会清空队列，此处可能遇到空队列
            if (filesQueue_.empty()) {
                mutex_.unlock();
                break;
            }

            auto f = filesQueue_.front();
            filesQueue_.pop_front();

            mutex_.unlock();

            doCalc_(f.first, f.second);
            dropFileObject_(f.second);
            emit fileDone(f.first);
        }
    });

    calcThread_->connect(calcThread_, &QThread::finished, [this](){
        emit finished(); // TODO: 等待readThread ???
    });

    calcThread_->start();

    emit started();
}


void KvPickOp::stop(bool wait)
{
    if (readThread_) {
        Q_ASSERT(calcThread_);

        readThread_->requestInterruption();
        calcThread_->requestInterruption();

        if (wait) {
            readThread_->wait();
            calcThread_->wait();
        }
    }
}


void KvPickOp::cleanFilesQueue_()
{
    mutex_.lock();
    for (auto i : filesQueue_)
        dropFileObject_(i.second);
    filesQueue_.clear();
    mutex_.unlock();
}

void KvPickOp::tryEmitResult_(const QString& filePath, bool isHole)
{
    if ((isHole && !pickNonHoles()) || (!isHole && pickNonHoles()))
        emit resultFound(-1, filePath); // 不分组 ，groupidx始终传入-1
}
